/*
 * Copyright (C) 2010 Fores Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.fores.midori.client.caller;

import jp.fores.midori.client.exception.AllTargetRPCFailureException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * 複数のサーバーに対して繰り返し回数指定付きでリモートプロシージャーコールを行うようにするための、{@link RPCClientCaller}のラッパークラス。<br>
 * <i>(Decoratorパターンを使用)</i><br>
 * (このクラスの全てのメソッドはスレッドセーフです。)<br>
 */
public class RepeatRPCClientCaller extends AbstractRPCClientCaller {
	//==========================================================
	//定数

	/**
	 * ログ出力用
	 */
	private static final Log log = LogFactory.getLog(RepeatRPCClientCaller.class);

	/**
	 * リトライまでの待ち時間のデフォルト値(単位=ミリ秒)
	 */
	public static final int DEFAULT_RETRY_MILLISECONDS = 100;

	/**
	 * リトライまでの最大待ち時間のデフォルト値(単位=ミリ秒)
	 */
	public static final int DEFAULT_MAX_RETRY_MILLISECONDS = 5000;


	//==========================================================
	//フィールド

	/**
	 * 複数のサーバーに対してリモートプロシージャーコールを行うインターフェース
	 */
	protected final RPCClientCaller caller;

	/**
	 * 繰り返し回数(0以下の値が指定された場合は成功するまで繰り返す)
	 */
	public final int repeatCount;

	/**
	 * リトライまでの待ち時間(単位=ミリ秒)
	 */
	public final int retryMilliseconds;

	/**
	 * リトライまでの待ち時間を倍々にしていく方式を使用するかどうかのフラグ
	 */
	public final boolean isRetryWaitIncreasing;

	/**
	 * リトライまでの最大待ち時間(単位=ミリ秒)
	 */
	public final int retryMaxMilliseconds;


	//==========================================================
	//メソッド

	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//コンストラクタ

	/**
	 * 必要最低限の情報だけを指定するコンストラクタです。<br>
	 * デフォルトの待ち時間で、リトライまでの待ち時間を倍々にしていく方式を使用します。<br>
	 *
	 * @param caller 複数のサーバーに対してリモートプロシージャーコールを行うインターフェース
	 * @param repeatCount 繰り返し回数(0以下の値が指定された場合は成功するまで繰り返す)
	 */
	public RepeatRPCClientCaller(RPCClientCaller caller, int repeatCount) {
		//実際に処理を行うコンストラクタを呼び出す
		//(リトライまでの待ち時間にはデフォルト値の定数を指定する)
		this(caller, repeatCount, DEFAULT_RETRY_MILLISECONDS);
	}

	/**
	 * URLのリストを再取得するまでの待ち時間を指定するタイプのコンストラクタです。<br>
	 * リトライまでの待ち時間を倍々にしていく方式を使用します。<br>
	 * URLのリストを再取得するまでの待ち時間が0以下の場合は、待ち処理を行わずに即座に再取得します。<br>
	 *
	 * @param caller 複数のサーバーに対してリモートプロシージャーコールを行うインターフェース
	 * @param repeatCount 繰り返し回数(0以下の値が指定された場合は成功するまで繰り返す)
	 * @param retryMilliseconds リトライまでの待ち時間(単位=ミリ秒)
	 */
	public RepeatRPCClientCaller(RPCClientCaller caller, int repeatCount,
			int retryMilliseconds) {
		//実際に処理を行うコンストラクタを呼び出す
		//(リトライまでの待ち時間を倍々にしていく方式を使用するかどうかのフラグにはtrue, リトライまでの最大待ち時間にはデフォルト値の定数を指定する)
		this(caller, repeatCount, retryMilliseconds, true, DEFAULT_MAX_RETRY_MILLISECONDS);
	}

	/**
	 * 細かく指定するタイプのコンストラクタです。<br>
	 * URLのリストを再取得するまでの待ち時間が0以下の場合は、待ち処理を行わずに即座に再取得します。<br>
	 * リトライまでの最大待ち時間は、リトライまでの待ち時間が0より大きく、かつリトライまでの待ち時間を倍々にしていく方式の場合のみ有効となります。<br>
	 *
	 * @param caller 複数のサーバーに対してリモートプロシージャーコールを行うインターフェース
	 * @param repeatCount 繰り返し回数(0以下の値が指定された場合は成功するまで繰り返す)
	 * @param retryMilliseconds リトライまでの待ち時間(単位=ミリ秒)
	 * @param isRetryWaitIncreasing リトライまでの待ち時間を倍々にしていく方式を使用するかどうかのフラグ(true=倍々にしていく, false=そのまま)
	 * @param retryMaxMilliseconds リトライまでの最大待ち時間
	 */
	public RepeatRPCClientCaller(RPCClientCaller caller, int repeatCount,
			int retryMilliseconds, boolean isRetryWaitIncreasing,
			int retryMaxMilliseconds) {
		//==========================================================
		//引数の値をフィールドに設定

		//複数のサーバーに対してリモートプロシージャーコールを行うインターフェース
		this.caller = caller;

		//繰り返し回数
		this.repeatCount = repeatCount;

		//リトライまでの待ち時間
		this.retryMilliseconds = retryMilliseconds;

		//リトライまでの待ち時間を倍々にしていく方式を使用するかどうかのフラグ
		this.isRetryWaitIncreasing = isRetryWaitIncreasing;

		//リトライまでの最大待ち時間
		this.retryMaxMilliseconds = retryMaxMilliseconds;
	}


	//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	//抽象メソッドの実装

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object remoteCall(URLListGetter urlListGetter, String methodName,
			Object... args) throws Exception {
		//残りの繰り返し回数
		//(初期値にはフィールドの繰り返し回数を設定)
		int remainCount = this.repeatCount;

		//現在のリトライまでの待ち時間
		//(初期値にはフィールドの繰り返し回数を設定)
		int waitTime = this.retryMilliseconds;

		//処理に成功するか繰り返し回数がなくなるまで繰り返したいので、無限ループをまわす
		while (true) {
			//実行中のスレッドに割り込みが発生している場合
			//(割り込みを検知する処理を入れておかないと、いつまでたってもスレッドが停止しないことがあるため)
			if (Thread.currentThread().isInterrupted()) {
				//デバッグログが有効な場合
				if (log.isDebugEnabled()) {
					//デバッグログ出力
					log.debug("複数のサーバーに対して繰り返し回数指定付きでリモートプロシージャーコールを行っている途中で、リモートプロシージャーコールの途中で割り込みを検知しました。");
				}

				//割り込みが発生した場合の例外を投げる
				throw new InterruptedException("複数のサーバーに対して繰り返し回数指定付きでリモートプロシージャーコールを行っている途中で、割り込みが発生しました。");
			}

			try {
				//複数のサーバーに対してリモートプロシージャーコールを行うインターフェースに処理を委譲する
				return this.caller.remoteCall(urlListGetter, methodName, args);
			}
			//全てのRPCの対象に対して呼び出しが失敗した場合
			catch (AllTargetRPCFailureException e) {
				//残りの繰り返し回数が1の場合
				//(今回で最後の繰り返しが終わった場合)
				if (remainCount == 1) {
					//全てのRPCの対象に対して呼び出しが失敗した場合の例外を投げる
					throw new AllTargetRPCFailureException(this.repeatCount
							+ "回ずつRPCを行いましたが、全て呼び出しが失敗しました。");
				}

				//残りの繰り返し回数が1より大きい場合
				if (remainCount > 1) {
					//残りの繰り返し回数を-1する
					remainCount--;
				}


				//==========================================================
				//リトライまでの待ち時間分だけスリープする

				//リトライまでの待ち時間が0より大きい場合
				if (waitTime > 0) {
					//デバッグログが有効な場合
					if (log.isDebugEnabled()) {
						//デバッグログ出力
						log.debug("リトライする前に、" + waitTime + " ミリ秒スリープします。");
					}

					//リトライまでの待ち時間の分だけ処理をスレッドを停止する
					Thread.sleep(waitTime);

					//リトライまでの待ち時間を倍々にしていく方式の場合
					if (this.isRetryWaitIncreasing) {
						//現在のリトライまでの待ち時間を倍にする
						waitTime *= 2;

						//現在のリトライまでの待ち時間が最大待ち時間を超えないように補正する
						if (waitTime > this.retryMaxMilliseconds) {
							waitTime = this.retryMaxMilliseconds;
						}
					}
				}


				//==========================================================
				//処理を継続させるために、デバッグログを出力するだけで例外をもみ消す

				//デバッグログが有効な場合
				if (log.isDebugEnabled()) {
					//デバッグログ出力
					log.debug("全てのRPCの対象に対して呼び出しが失敗しましたが、処理を繰り返します。");
				}
			}
		}
	}

}
